package com.jiaomatech.doc.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.jiaomatech.doc.dto.Api;
import com.jiaomatech.doc.dto.Doc;
import com.jiaomatech.doc.dto.ReqDto;
import com.jiaomatech.doc.dto.ResDto;
import com.jiaomatech.doc.dto.Tag;
import com.jiaomatech.doc.service.WordService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class WordServiceImpl implements WordService {

    public static final String DEF = "#/definitions/";
    public static final String DEF_SHADOW = "TTTTOOOO";
    public static final String REF = "\\$ref";
    public static final String REF_SHADOW = "REF";
    @Autowired
    private RestTemplate restTemplate;
    private Set<String> stackOverflowErrorName = new HashSet<>();

    @Override
    public synchronized Doc table(String swaggerUrl) {
        stackOverflowErrorName = new HashSet<>();
        String json = restTemplate.getForObject(swaggerUrl, String.class);
        json = json.replaceAll(DEF, DEF_SHADOW);
        json = json.replaceAll(REF, REF_SHADOW);
        JSONObject jsonObject = JSON.parseObject(json);
        String basePath = jsonObject.getString("basePath");
        JSONObject paths = jsonObject.getJSONObject("paths");
        JSONObject definitions = jsonObject.getJSONObject("definitions");
        JSONArray tags = jsonObject.getJSONArray("tags");
        List<Api> apis = paths.entrySet().stream()
            .map(entry -> {
                String path = entry.getKey();
                JSONObject value = (JSONObject) entry.getValue();
                String type = getType(value);
                JSONObject detail = value.getJSONObject(type);
                if (Objects.isNull(detail)) {
                    return null;
                }
                String tag = getFirst(detail, "tags");
                String summary = detail.getString("summary");
                String description = detail.getString("description");
                String operationId = detail.getString("operationId");
                String consumes = getFirst(detail, "consumes");
                String produces = getFirst(detail, "produces");
                JSONArray req = detail.getJSONArray("parameters");
                JSONObject res = detail.getJSONObject("responses");
                Api api = new Api();
                api.setPath(path);
                api.setType(type);
                api.setTag(tag);
                api.setConsumes(StringUtils.defaultIfBlank(consumes, "application/json;charset=UTF-8"));
                api.setDescription(description);
                api.setOperationId(operationId);
                api.setProduces(produces);
                api.setSummary(summary);
                if (Objects.nonNull(req)) {
                    List<ReqDto> reqDtos = req.stream().map(one -> {
                        JSONObject r = (JSONObject) one;
                        String r_name = r.getString("name");
                        String r_in = r.getString("in");
                        String r_description = r.getString("description");
                        boolean r_required = Optional.ofNullable(r.getBoolean("required")).orElse(false);
                        String r_type = r.getString("type");
                        ReqDto reqDto = new ReqDto();
                        reqDto.setIn(r_in);
                        reqDto.setDescription(r_description);
                        reqDto.setName(r_name);
                        reqDto.setRequired(r_required);
                        reqDto.setType(r_type);
                        if ("body".equals(r_in)) {
                            reqDto.setType(r_name);
                            JSONObject schema = r.getJSONObject("schema");
                            List<ResDto> resDtos = getResDtos(schema, definitions);
                            reqDto.setBodyDtoList(resDtos);
                        }
                        return reqDto;
                    }).collect(Collectors.toList());
                    api.setReqDtos(reqDtos);
                }

                // 响应
                JSONObject ok = res.getJSONObject("200");
                JSONObject schema = ok.getJSONObject("schema");
                String schemaType = Optional.ofNullable(schema).orElse(new JSONObject()).getString("type");
                List<ResDto> resDtos = getResDtos(schema, definitions);
                api.setResDtos(resDtos);
                Object demo = getDemo(resDtos, schemaType);
                api.setDemo(JSON.toJSONString(demo, true));
                return api;
            })
            .filter(Objects::nonNull)
            .collect(Collectors.toList());
        Doc doc = new Doc();
        doc.setBasePath(basePath);
        doc.setApis(apis);
        List<Tag> tagList = tags.toJavaList(JSONObject.class).stream()
            .map(t -> {
                Tag tag = new Tag();
                tag.setName(t.getString("name"));
                tag.setDescription(t.getString("description"));
                tag.setApis(apis.stream().filter(api -> tag.getName().equals(api.getTag())).collect(Collectors.toList()));
                return tag;
            }).collect(Collectors.toList());
        doc.setTags(tagList);
        return doc;
    }

    /**
     * 拿到对应的DTO
     *
     * @param schema
     * @param definitions
     * @return
     */
    private List<ResDto> getResDtos(JSONObject schema, JSONObject definitions) {
        if (Objects.isNull(schema)) {
            return new ArrayList<>();
        }
        String schemaType = schema.getString("type");
        String refString = Optional.ofNullable(schema.getString(REF_SHADOW)).orElse("");
        JSONObject items = schema.getJSONObject("items");
        if (Objects.nonNull(items)) {
            refString = Optional.ofNullable(items.getString(REF_SHADOW)).orElse("");
        }
        String dtoType = refString.replace(DEF_SHADOW, "").replace("ResponseEntity", "");
        JSONObject resJson = definitions.getJSONObject(dtoType);
        List<ResDto> resDtos = new ArrayList<>();
        if (Objects.nonNull(resJson)) {
            String title = resJson.getString("title");
            JSONObject resProperties = resJson.getJSONObject("properties");
            if (Objects.nonNull(resProperties)) {
                resDtos = resProperties.keySet().stream()
                    .map(key -> {
                        JSONObject obj = resProperties.getJSONObject(key);
                        ResDto resDto = new ResDto();
                        resDto.setField(key);
                        resDto.setTitle(title);
                        resDto.setDtoType(dtoType);
                        resDto.setDescription(obj.getString("description"));
                        String format = obj.getString("format");
                        resDto.setFormat(format);
                        resDto.setType(obj.getString("type"));
                        if ("date-time".equals(format)) {
                            resDto.setType("Date");
                        }
                        JSONObject insideSchema = obj.getJSONObject("items");
                        if (Objects.nonNull(insideSchema)) {
                            String insideObj = insideSchema.getString(REF_SHADOW);
                            if (Objects.nonNull(insideObj)) {
                                String insideDtoName = insideObj.replace(DEF_SHADOW, "");
                                String flowName = dtoType + " --- " + insideDtoName;
                                String flowName2 = insideDtoName + " --- " + dtoType;
                                if (!stackOverflowErrorName.contains(flowName) && !stackOverflowErrorName.contains(flowName2)) {
                                    stackOverflowErrorName.add(flowName);
                                    List<ResDto> inside = getResDtos(insideSchema, definitions);
                                    resDto.setInside(inside);
                                }
                            } else {
                                String insideSchemaType = insideSchema.getString("type");
                                resDto.setInsideSchemaType(insideSchemaType);
                                resDto.setDescription(obj.getString("description")
                                    + " 集合内类型：" + insideSchema.getString("type"));
                            }
                        }
                        return resDto;
                    }).collect(Collectors.toList());
            }
        }
        return resDtos;
    }

    /**
     * 示例.
     *
     * @param resDtos
     * @return
     */
    private Object getDemo(List<ResDto> resDtos, String schemaType) {
        Map<String, Object> demo = new HashMap<>();
        if (!CollectionUtils.isEmpty(resDtos)) {
            for (ResDto rd : resDtos) {
                String field = rd.getField();
                String fieldType = rd.getType();
                if ("statusCodeValue".equals(field) || "statusCode".equals(field)) {
                    continue;
                }
                if ("Date".equals(fieldType)) {
                    demo.put(field, "2018-12-12 00:00:00");
                }
                if ("string".equals(fieldType)) {
                    demo.put(field, "string");
                }
                if ("integer".equals(fieldType)) {
                    demo.put(field, new Random().nextInt(100) * 100);
                }
                if (Objects.nonNull(rd.getInsideSchemaType())) {
                    demo.put(field, Arrays.asList(rd.getInsideSchemaType(), rd.getInsideSchemaType()));
                }
                if (Objects.nonNull(rd.getInside())) {
                    demo.put(field, getDemo(rd.getInside(), fieldType));
                }
            }
        }
        if ("array".equals(schemaType)) {
            ArrayList<Map<String, Object>> arrayList = new ArrayList<Map<String, Object>>(1);
            arrayList.add(demo);
            return arrayList;
        } else {
            return demo;
        }
    }

    /**
     * 只取第一个.
     *
     * @param detail
     * @param key
     * @return
     */
    private String getFirst(JSONObject detail, String key) {
        JSONArray tags = detail.getJSONArray(key);
        if (Objects.isNull(tags)) {
            return "";
        }
        String tag = tags.toString();
        if (tags.size() > 0) {
            tag = tags.get(0).toString();
        }
        return tag;
    }

    /**
     * 拿到类型.
     *
     * @param value
     * @return
     */
    private String getType(JSONObject value) {
        return value.entrySet().stream().findFirst().map(Map.Entry::getKey).orElse("");
    }
}
